<?php

namespace Ylt\AdsbPhp;

require_once "common.php";
use InvalidArgumentException;

class ADSBParser extends CommonParser
{
    //解析所有报文
    public function parserAll(string $adsbMessage): array
    {
        $head = $this->extractHead($adsbMessage);
        $icao = $this->parserICAO($adsbMessage);
        $data = $this->getMeData($adsbMessage);
        $pi = $this->getPI($adsbMessage);
        $head_bin2 = $this->hexToBinaryWithPadding($head);
        $icao_bin2 = $this->hexToBinaryWithPadding($icao);
        $data_bin2 = $this->hexToBinaryWithPadding($data);
        $pi_bin2 = $this->hexToBinaryWithPadding($pi);
        $all_bin2 = $head_bin2 . $icao_bin2 . $data_bin2 . $pi_bin2;
        $check = self::adsbCrcCheck($all_bin2);
        if (!$check) {
            throw new InvalidArgumentException("ads-b消息校验失败，无效的ads-b消息");
        }
        $typeCode = bindec(substr($data_bin2, 0, 5));
        return [
            'origin_data' => $data,
            'icao' => $icao,
            'df' =>$this->getDF($adsbMessage),
            'receive_time' => time(),
            'type_code' => $typeCode,
            'type' => $this->parserType($typeCode),
            'aircraft' => $this->parserAircraft($typeCode,$data_bin2),
            'position' => $this->parserPosition($typeCode,$data_bin2,$icao),
            'velocity' => $this->parserVelocity($typeCode, $data_bin2),
            'altitude' => $this->parserAltitude($data_bin2),
            'squawk' => $this->parserSquawk($data_bin2),
            'emergency' => $this->parserEmergency($data_bin2),
            'spi' => $this->parserSPI($data_bin2),
            'is_on_ground' => $this->parserIsOnGround($data_bin2),
            'timestamp' => $this->parserTimestamp($data_bin2),
            'callsign' => $this->parserCallsign($data_bin2),
            'status' => $this->parserStatus($typeCode),
        ];
    }

    //解析icao报文
    public function parserICAO(string $data)
    {
        // 检查输入数据长度是否足够
        if (strlen($data) < 6) {
            return null;
        }
        return substr($data, 2, 6);
    }

    // 解析报文类型
    public  function parserType(int $typeCode)
    {

        // 根据不同的类型值返回对应的类型描述
        switch ($typeCode) {
            case 1:
            case 2:
            case 3:
            case 4:
                return 'Identification and Category';
            case 5:
            case 6:
            case 7:
            case 8:
                return 'Surface Position';
            case 9:
            case 10:
            case 11:
            case 12:
            case 13:
            case 14:
            case 15:
            case 16:
            case 17:
            case 18:
                return 'Airborne Position';
            case 19:
                return 'Airborne Velocity';
            case 20:
            case 21:
            case 22:
                return 'Airborne Position (Precise)';
            default:
                return 'Unknown Type';
        }
    }

    /**
     * 解析航空器相关信息
     *
     * @param int $typeCode
     * @param string $data_bin2
     * @return string|null 解析出的航空器信息，解析失败返回 null
     */
    public  function parserAircraft(int $typeCode,$data_bin2): ?string
    {
        if ($typeCode >= 1 && $typeCode <= 4) {
            $map_code = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######';
            $typeBits = substr($data_bin2, 8);
            $length = strlen($typeBits);
            $fly_num = '';
            for ($i = 0; $i < $length; $i += 6) {
                $num_code = bindec(substr($typeBits, $i, 6));
                $fly_num .= $map_code[$num_code];
            }
            $fly_num = str_replace('_', '', $fly_num);
            return str_replace('#', '', $fly_num);
        }
        return null;
    }

    private  function getDF(string $data)
    {
        // 检查输入数据长度是否足够
        if (strlen($data) < 2) {
            return null;
        }
        // 提取第 1 - 2 个字符作为 DF
        $dfPart = substr($data, 0, 2);
        $head_bin2 = base_convert($dfPart, 16, 2);
        $df_bin2 = substr($head_bin2, 0, 5);
        return bindec($df_bin2);
    }

    public static function adsbCrcCheck($msg)
    {
        $generator = "1111111111111010000001001";
        // 将消息字符串转换为字符数组，方便进行位操作
        $msgArray = str_split($msg);
        // 获取消息长度
        $msgLength = count($msgArray);
        // 生成多项式的长度
        $generatorLength = strlen($generator);

        // 循环异或操作
        for ($i = 0; $i < $msgLength - 24; $i++) {
            if ($msgArray[$i] == '1') {
                for ($j = 0; $j < $generatorLength; $j++) {
                    // 进行异或操作，同时处理数组索引越界的情况
                    $index = $i + $j;
                    if ($index < $msgLength) {
                        $msgArray[$index] = ($msgArray[$index] == $generator[$j]) ? '0' : '1';
                    }
                }
            }
        }

        // 提取最后的 24 位作为 CRC 结果
        $CRC = implode('', array_slice($msgArray, -24));

        // 检查 CRC 是否为 0，如果不为 0，则认为消息损坏
        if (bindec($CRC) != 0) {
            return false;
        } else {
            return true;
        }
    }

    public function hexToBinaryWithPadding($hex)
    {
        $binary = decbin(hexdec($hex));
        $length = strlen($binary);
        $paddingLength = 8 - ($length % 8);
        if ($length % 8 != 0) {
            $binary = str_pad($binary, $length + $paddingLength, '0', STR_PAD_LEFT);
        }
        return $binary;
    }

    public function extractHead($adsbMessage)
    {
        return substr($adsbMessage, 0, 2);
    }

    public function getMeData($adsbMessage)
    {
        return substr($adsbMessage, 8, 14);
    }

    public function getPI($adsbMessage)
    {
        return substr($adsbMessage, -6);
    }

    private function parserPosition($typeCode,$data_bin2,$icao)
    {
        // 解析地表面位置消息
        if ($typeCode >= 5 && $typeCode <= 8||($typeCode >= 9 && $typeCode <= 18) || ($typeCode >= 20 && $typeCode <= 22)) {
            $dir=__DIR__.DIRECTORY_SEPARATOR.'position';
            if(!is_dir($dir)){
                mkdir($dir,0777);
            }
            $file_even_name = $dir.DIRECTORY_SEPARATOR.$icao . "even.txt";
            $file_odd_name=$dir.DIRECTORY_SEPARATOR.$icao.'odd.txt';
            $frame = substr($data_bin2, 21, 1);
            $lon = "";
            $lat = "";
            //0 -> Even frame
            if ($frame == 0) {
                $LAT_CPR_EVEN = bindec(substr($data_bin2, 22, 17)) / 131072;
                $LON_CPR_EVEN = bindec(substr($data_bin2, 39, 17)) / 131072;
                $even_arr=compact('LAT_CPR_EVEN', 'LON_CPR_EVEN');
                file_put_contents($file_even_name,json_encode($even_arr,JSON_UNESCAPED_UNICODE));
            }
            if ($frame == 1) {
                $LAT_CPR_ODD = bindec(substr($data_bin2, 22, 17)) / 131072;
                $LON_CPR_ODD = bindec(substr($data_bin2, 39, 17)) / 131072;
                $odd_arr=compact('LAT_CPR_ODD', 'LON_CPR_ODD');
                file_put_contents($file_odd_name,json_encode($odd_arr,JSON_UNESCAPED_UNICODE));
            }
            if(file_exists($file_even_name)&&file_exists($file_odd_name)){
                $even_data=json_decode(file_get_contents($file_even_name),true);
                $odd_data=json_decode(file_get_contents($file_odd_name),true);
                if(!empty($even_data)&&!empty($odd_data)){
                    $j = floor(59 * $even_data['LAT_CPR_EVEN'] - 60 * $odd_data['LAT_CPR_ODD'] + 0.5);
                    $dlate = 360 / 60;
                    $dlato = 360 / 59;
                    $late = floatval($dlate * (python_mod($j, 60) + $even_data['LAT_CPR_EVEN']));
                    $lato = floatval($dlato * (python_mod($j, 59) + $odd_data['LAT_CPR_ODD']));
                    if ($late >= 270) {
                        $late = $late - 360;
                    }
                    if ($lato >= 270) {
                        $lato = $lato - 360;
                    }
                    if ($late >= $lato) {
                        $lat = $late;
                    } else {
                        $lat = $lato;
                    }
                    $nl_late = cpr_nl($late);
                    $nl_lato = cpr_nl($lato);
                    if ($nl_late != $nl_lato) {
                        @unlink($file_odd_name);
                        @unlink($file_even_name);
                    } else {
                        //计算经度
                        if ($late > $lato) {
                            $ni = max(cpr_nl($late)-0, 1);
                            $dlon = 360 / $ni;
                            $m = floor($even_data['LON_CPR_EVEN'] * (cpr_nl($late) - 1) - $odd_data['LON_CPR_ODD'] * cpr_nl($late) + 0.5);
                            $lon = $dlon * (python_mod($m, $ni) + $even_data['LON_CPR_EVEN']);
                        }
                        if ($late < $lato) {
                            $ni = max(cpr_nl($lato) - 1, 1);
                            $dlon = 360 / $ni;
                            $m = floor($even_data['LON_CPR_EVEN'] * (cpr_nl($lato) - 1) - $odd_data['LON_CPR_ODD'] * cpr_nl($lato) + 0.5);
                            $lon = $dlon * (python_mod($m, $ni) + $odd_data['LON_CPR_ODD']);
                        }
                        if ($lon > 180) {
                            $lon = $lon - 360;
                        }
                        @unlink($file_odd_name);
                        @unlink($file_even_name);
                    }
                }
            }
            $altitude_meters = $this->parserAltitude($data_bin2);
            $arr = compact('lat', 'lon', 'altitude_meters');
            return compact('lat', 'lon', 'altitude_meters');
        }
        return null;
    }

    /**
     * 解析高度信息
     * @param string $data_bin2 二进制数据
     * @return float|null 高度（米）
     */
    public function parserAltitude($data_bin2)
    {
        // 检查数据是否有效
        if (strlen($data_bin2) < 20) {
            return null;
        }

        // 提取高度相关位
        $q_bit = substr($data_bin2, 4, 1);
        $altitude_bits = substr($data_bin2, 5, 12);

        // 处理不同类型的高度编码
        if ($q_bit == '1') {
            // 100英尺增量，气压高度
            $altitude_ft = bindec($altitude_bits) * 25 - 1000;
        } else {
            // 25英尺增量，几何高度
            $altitude_ft = bindec($altitude_bits) * 25;
        }

        // 转换为米
        return self::feetToMeters($altitude_ft);
    }

    /**
     * 解析速度信息
     * @param int $typeCode 类型码
     * @param string $data_bin2 二进制数据
     * @return array|null 速度信息数组
     */
    public function parserVelocity($typeCode, $data_bin2)
    {
        if ($typeCode != 19 && !($typeCode >= 20 && $typeCode <= 22)) {
            return null;
        }

        $result = [
            'ground_speed' => null,
            'heading' => null,
            'vertical_rate' => null,
            'vertical_rate_source' => null,
            'air_speed' => null,
            'air_speed_type' => null
        ];

        // 速度类型 (0-3)
        $speed_type = bindec(substr($data_bin2, 5, 3));

        // 地面速度和航向 (类型1或2)
        if ($speed_type == 1 || $speed_type == 2) {
            // 东西方向速度
            $ew_velocity = bindec(substr($data_bin2, 14, 10)) - 1;
            $ew_sign = substr($data_bin2, 13, 1);
            if ($ew_sign == 1) {
                $ew_velocity = -$ew_velocity;
            }

            // 南北方向速度
            $ns_velocity = bindec(substr($data_bin2, 25, 10)) - 1;
            $ns_sign = substr($data_bin2, 24, 1);
            if ($ns_sign == 1) {
                $ns_velocity = -$ns_velocity;
            }

            // 计算地面速度 (m/s)
            $ground_speed = sqrt(pow($ew_velocity, 2) + pow($ns_velocity, 2));
            $result['ground_speed'] = round($ground_speed, 2);

            // 计算航向
            if ($ew_velocity != 0 || $ns_velocity != 0) {
                $heading = rad2deg(atan2($ew_velocity, $ns_velocity));
                if ($heading < 0) {
                    $heading += 360;
                }
                $result['heading'] = round($heading, 2);
            }
        }

        // 空速和航向 (类型3或4)
        if ($speed_type == 3 || $speed_type == 4) {
            // 航向可用
            if (substr($data_bin2, 13, 1) == 1) {
                $heading = bindec(substr($data_bin2, 14, 10)) / 1024 * 360;
                $result['heading'] = round($heading, 2);
            }

            // 空速
            $air_speed = bindec(substr($data_bin2, 25, 10));
            $result['air_speed'] = $air_speed;
            $result['air_speed_type'] = (substr($data_bin2, 24, 1) == 0) ? 'IAS' : 'TAS';
        }

        // 垂直速度
        $vertical_rate_source = substr($data_bin2, 35, 1);
        $result['vertical_rate_source'] = ($vertical_rate_source == 0) ? '气压' : '几何';

        $vertical_sign = substr($data_bin2, 36, 1);
        $vertical_rate = bindec(substr($data_bin2, 37, 9)) - 1;
        $vertical_rate *= 64; // 英尺/分钟

        if ($vertical_sign == 1) {
            $vertical_rate = -$vertical_rate;
        }

        // 转换为米/秒
        $result['vertical_rate'] = round($vertical_rate * 0.00508, 2);

        return $result;
    }

    /**
     * 解析二次雷达代码 (Squawk)
     * @param string $data_bin2 二进制数据
     * @return string|null Squawk代码
     */
    public function parserSquawk($data_bin2)
    {
        if (strlen($data_bin2) < 24) {
            return null;
        }

        $squawk_bits = substr($data_bin2, 8, 12);
        $squawk = sprintf(
            '%d%d%d%d',
            bindec(substr($squawk_bits, 0, 3)),
            bindec(substr($squawk_bits, 3, 3)),
            bindec(substr($squawk_bits, 6, 3)),
            bindec(substr($squawk_bits, 9, 3))
        );

        return $squawk;
    }

    /**
     * 解析紧急状态
     * @param string $data_bin2 二进制数据
     * @return array|null 紧急状态信息
     */
    public function parserEmergency($data_bin2)
    {
        if (strlen($data_bin2) < 11) {
            return null;
        }

        $emergency = substr($data_bin2, 8, 3);
        $emergency_code = bindec($emergency);

        $emergency_types = [
            0 => 'No emergency',
            1 => 'General emergency',
            2 => 'Medical emergency',
            3 => 'Minimum fuel',
            4 => 'No communication',
            5 => 'Unlawful interference',
            6 => 'Downed aircraft',
            7 => 'Reserved'
        ];

        return [
            'code' => $emergency_code,
            'description' => $emergency_types[$emergency_code] ?? 'Unknown'
        ];
    }

    /**
     * 解析特殊位置识别 (SPI)
     * @param string $data_bin2 二进制数据
     * @return bool|null SPI状态
     */
    public function parserSPI($data_bin2)
    {
        if (strlen($data_bin2) < 11) {
            return null;
        }

        return substr($data_bin2, 11, 1) == '1';
    }

    /**
     * 解析是否在地面
     * @param string $data_bin2 二进制数据
     * @return bool|null 地面状态
     */
    public function parserIsOnGround($data_bin2)
    {
        if (strlen($data_bin2) < 12) {
            return null;
        }

        return substr($data_bin2, 12, 1) == '1';
    }

    /**
     * 解析时间戳
     * @param string $data_bin2 二进制数据
     * @return int|null 时间戳
     */
    public function parserTimestamp($data_bin2)
    {
        if (strlen($data_bin2) < 70) {
            return null;
        }

        $timestamp_bits = substr($data_bin2, 60, 10);
        return bindec($timestamp_bits);
    }

    /**
     * 解析呼号
     * @param string $data_bin2 二进制数据
     * @return string|null 呼号
     */
    public function parserCallsign($data_bin2)
    {
        if (strlen($data_bin2) < 56) {
            return null;
        }

        $callsign_bits = substr($data_bin2, 16, 40);
        $map = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######';
        $callsign = '';

        for ($i = 0; $i < 40; $i += 6) {
            $char_index = bindec(substr($callsign_bits, $i, 6));
            if ($char_index < strlen($map)) {
                $callsign .= $map[$char_index];
            }
        }

        // 移除填充字符
        $callsign = str_replace(['#', '_'], '', $callsign);
        return trim($callsign);
    }

    /**
     * 解析状态信息
     * @param int $typeCode 类型码
     * @return array 状态信息
     */
    public function parserStatus($typeCode)
    {
        $status = [
            'valid' => true,
            'type' => $this->parserType($typeCode),
            'type_code' => $typeCode
        ];

        // 根据类型码添加特定状态
        if ($typeCode >= 1 && $typeCode <= 4) {
            $status['category'] = 'Identification';
        } elseif ($typeCode >= 5 && $typeCode <= 8) {
            $status['category'] = 'Surface Position';
        } elseif ($typeCode >= 9 && $typeCode <= 18) {
            $status['category'] = 'Airborne Position';
            $status['barometric_altitude'] = true;
        } elseif ($typeCode == 19) {
            $status['category'] = 'Airborne Velocity';
        } elseif ($typeCode >= 20 && $typeCode <= 22) {
            $status['category'] = 'Airborne Position';
            $status['gnss_altitude'] = true;
        } else {
            $status['valid'] = false;
            $status['error'] = 'Unknown type code';
        }

        return $status;
    }

    public function getQyHeight($data_bin2)
    {
        $alt = substr($data_bin2, 8, 12);
        $alt_new = substr($alt, 0, 6) . substr($alt, 7);
        $alt = bindec($alt_new) * 25 - 1000;
        return self::feetToMeters($alt);
    }
    public static function feetToMeters($feet)
    {
        return $feet * 0.3048;
    }

}